/*
 * WorkflowBuilder.java
 *
 * Created on Jun 5, 2007, 11:54:07 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.infomancers.workflow.dynamic;

import com.infomancers.collections.Bag;
import com.infomancers.collections.CollectionsAlgorithms;
import com.infomancers.collections.predicates.*;
import com.infomancers.collections.HashBag;
import com.infomancers.collections.Predicate;
import com.infomancers.workflow.FlowManager;
import com.infomancers.workflow.Place;
import com.infomancers.workflow.Transition;
import com.infomancers.workflow.Workflow;
import com.infomancers.workflow.channels.ChannelFactory;
import com.infomancers.workflow.channels.InputChannel;
import com.infomancers.workflow.channels.OutputChannel;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;

/**
 *
 * @author freakazoid
 */
public class WorkflowBuilder {
    private enum Direction {
        FromPlace,ToPlace
    }
    
    private static class TransitionArc {
        public Place place;
        public Transition transition;
        public Direction direction;
        
        public TransitionArc(Place place, Transition transition, Direction direction) {
            this.place = place;
            this.transition = transition;
            this.direction = direction;
        }
        
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            
            if (getClass() != obj.getClass()) {
                return false;
            }
            
            final TransitionArc other = (TransitionArc) obj;
            if (this.place != other.place && (this.place == null || !this.place.equals(other.place))) {
                return false;
            }
            if (this.transition != other.transition && (this.transition == null || !this.transition.equals(other.transition))) {
                return false;
            }
            if (this.direction != other.direction && (this.direction == null || !this.direction.equals(other.direction))) {
                return false;
            }
            return true;
        }
        
        public int hashCode() {
            int hash = 5;
            hash = 79 * hash + (this.place != null ? this.place.hashCode() : 0);
            hash = 79 * hash + (this.transition != null ? this.transition.hashCode() : 0);
            hash = 79 * hash + (this.direction != null ? this.direction.hashCode() : 0);
            return hash;
        }
        private static Object Direction;
        
        
    }
    
    private static class ChannelArc {
        public Place source;
        public ChannelFactory channel;
        public Direction direction;
        
        public ChannelArc(Place source, ChannelFactory channel, Direction direction) {
            this.source = source;
            this.channel = channel;
            this.direction = direction;
        }
        
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ChannelArc other = (ChannelArc) obj;
            if (this.source != other.source && (this.source == null || !this.source.equals(other.source))) {
                return false;
            }
            if (this.channel != other.channel && (this.channel == null || !this.channel.equals(other.channel))) {
                return false;
            }
            if (this.direction != other.direction && (this.direction == null || !this.direction.equals(other.direction))) {
                return false;
            }
            return true;
        }
        
        public int hashCode() {
            int hash = 7;
            hash = 97 * hash + (this.source != null ? this.source.hashCode() : 0);
            hash = 97 * hash + (this.channel != null ? this.channel.hashCode() : 0);
            hash = 97 * hash + (this.direction != null ? this.direction.hashCode() : 0);
            return hash;
        }
    }
    
    
    private static Predicate<Place> isDynamicPlacePredicate =
            new IsInstanceOfPredicate<Place, DynamicPlace>(DynamicPlace.class);
    private static Predicate<Place> isNotDynamicPlacePredicate =
            new NegativePredicate<Place>(isDynamicPlacePredicate);
    
    private Collection<Place> places = new HashSet<Place>();
    private Collection<Transition> transitions = new HashSet<Transition>();
    private Collection<TransitionArc> transitionArcs = new HashSet<TransitionArc>();
    private Collection<ChannelArc> channelArcs = new HashSet<ChannelArc>();
    
    public Workflow createWorkflow(Place input, Place output, FlowManager manager) {
        if (input != null && !places.contains(input)) {
            throw new IllegalArgumentException("input must be already added to the flow");
        }
        
        if (output != null && !places.contains(output)) {
            throw new IllegalArgumentException("output must be already added to the flow");
        }
        
        Workflow workflow = new Workflow();
        workflow.setFlowManager(manager);
        workflow.setInput(input);
        workflow.setOutput(output);
        
        // create bags of outputs and inputs for each place and transition according
        // to the arcs
        Bag<Place, Transition> placeOutputs = new HashBag<Place, Transition>();
        Bag<Transition, Place> transitionOutputs = new HashBag<Transition, Place>();
        Bag<Transition, Place> transitionInputs = new HashBag<Transition, Place>();
        
        for (TransitionArc a : transitionArcs) {
            switch (a.direction) {
            case FromPlace:
                placeOutputs.add(a.place, a.transition);
                transitionInputs.add(a.transition, a.place);
                break;
            case ToPlace:
                transitionOutputs.add(a.transition, a.place);
                break;
            }
        }
        
        // set dynamic places' delegates
        for (Place p : places) {
            if (p instanceof DynamicPlace) {
                DynamicPlace dp = (DynamicPlace)p;
                Place delegatePlace = CollectionsAlgorithms.findItem(
                        new And<Place>(isNotDynamicPlacePredicate, dp.getPredicate()), places);
                dp.setPlace(delegatePlace);
            }
        }
        
        // set all places' outputs
        for (Place p : places) {
            Collection<Transition> outputs = placeOutputs.getItems(p);
            
            if (outputs != null)
                p.setOutputTransitions(new LinkedList(outputs));
        }
        
        // set flow manager for all transitions, and adjust inputs/outputs
        for (Transition t : transitions) {
            t.setFlowManager(manager);
            t.setInputPlaces(transitionInputs.getItems(t));
            t.setOutputPlaces(transitionOutputs.getItems(t));
        }
        
        // create channels
        for (ChannelArc arc : channelArcs) {
            switch (arc.direction) {
            case FromPlace:
                InputChannel inputChannel = arc.channel.createInput();
                inputChannel.setSource(arc.source);
                break;
            case ToPlace:
                OutputChannel outputChannel = arc.channel.createOutput();
                outputChannel.setTarget(output);
                break;
            }
        }
        
        return workflow;
    }
    
    public void addConnection(Place place, Transition transition) {
        places.add(place);
        transitions.add(transition);
        transitionArcs.add(new TransitionArc(place, transition, WorkflowBuilder.Direction.FromPlace));
    }
    
    public void addConnection(Transition transition, Place place) {
        places.add(place);
        transitions.add(transition);
        transitionArcs.add(new TransitionArc(place, transition,WorkflowBuilder.Direction.ToPlace));
    }
    
    public void addConnection(Place source, ChannelFactory channel) {
        places.add(source);
        channelArcs.add(new ChannelArc(source, channel, Direction.FromPlace));
    }
    
    public void addConnection(ChannelFactory channel, Place target) {
        places.add(target);
        channelArcs.add(new ChannelArc(target, channel, Direction.ToPlace));
    }

    public void addWorkflow(Workflow workflow) {
        for (Place p : workflow.getPlaces()) {
            for (Transition t : p.getOutputTransitions()) {
                // add all input places
                for (Place input : t.getInputPlaces()) {
                    addConnection(input, t);
                }
                
                // add all output places
                for (Place output : t.getOutputPlaces()) {
                    addConnection(t, output);
                }
            }
        }
    }
    
    public void addWorkflowBuilder(WorkflowBuilder builder) {
        transitionArcs.addAll(builder.transitionArcs);
        channelArcs.addAll(builder.channelArcs);
        transitions.addAll(builder.transitions);
        places.addAll(builder.places);
    }
    
    public void addDisconnectedPlace(Place place) {
        places.add(place);
    }
}
